feat: initial JaniChat messenger — PWA, WebSocket, admin panel
This commit is contained in:
121
frontend/src/store/index.ts
Normal file
121
frontend/src/store/index.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { create } from 'zustand';
|
||||
import { Chat, Message, User } from '../types';
|
||||
|
||||
interface AppStore {
|
||||
user: User | null;
|
||||
chats: Chat[];
|
||||
activeChat: Chat | null;
|
||||
messages: Record<string, Message[]>;
|
||||
onlineUsers: Set<string>;
|
||||
typingUsers: Record<string, string[]>;
|
||||
connected: boolean;
|
||||
|
||||
setUser: (user: User | null) => void;
|
||||
setChats: (chats: Chat[]) => void;
|
||||
updateChat: (chat: Partial<Chat> & { id: string }) => void;
|
||||
setActiveChat: (chat: Chat | null) => void;
|
||||
setMessages: (chatId: string, messages: Message[]) => void;
|
||||
prependMessages: (chatId: string, messages: Message[]) => void;
|
||||
addMessage: (message: Message) => void;
|
||||
updateMessage: (message: Message) => void;
|
||||
removeMessage: (chatId: string, messageId: string) => void;
|
||||
setOnline: (userId: string, online: boolean) => void;
|
||||
setTyping: (chatId: string, userId: string, typing: boolean) => void;
|
||||
setConnected: (v: boolean) => void;
|
||||
markRead: (chatId: string) => void;
|
||||
logout: () => void;
|
||||
}
|
||||
|
||||
export const useStore = create<AppStore>((set, get) => ({
|
||||
user: null,
|
||||
chats: [],
|
||||
activeChat: null,
|
||||
messages: {},
|
||||
onlineUsers: new Set(),
|
||||
typingUsers: {},
|
||||
connected: false,
|
||||
|
||||
setUser: (user) => set({ user }),
|
||||
|
||||
setChats: (chats) => set({ chats }),
|
||||
|
||||
updateChat: (partial) => set(state => ({
|
||||
chats: state.chats.map(c => c.id === partial.id ? { ...c, ...partial } : c),
|
||||
activeChat: state.activeChat?.id === partial.id ? { ...state.activeChat, ...partial } : state.activeChat,
|
||||
})),
|
||||
|
||||
setActiveChat: (chat) => set({ activeChat: chat }),
|
||||
|
||||
setMessages: (chatId, messages) => set(state => ({
|
||||
messages: { ...state.messages, [chatId]: messages }
|
||||
})),
|
||||
|
||||
prependMessages: (chatId, messages) => set(state => ({
|
||||
messages: {
|
||||
...state.messages,
|
||||
[chatId]: [...messages, ...(state.messages[chatId] || [])]
|
||||
}
|
||||
})),
|
||||
|
||||
addMessage: (message) => set(state => {
|
||||
const existing = state.messages[message.chatId] || [];
|
||||
if (existing.find(m => m.id === message.id)) return state;
|
||||
const updated = [...existing, message];
|
||||
// Update chat last message
|
||||
const chats = state.chats.map(c =>
|
||||
c.id === message.chatId
|
||||
? { ...c, lastMessage: message.content, lastMessageAt: message.createdAt,
|
||||
unreadCount: state.activeChat?.id === message.chatId ? 0 : c.unreadCount + 1 }
|
||||
: c
|
||||
).sort((a, b) => {
|
||||
const ta = a.lastMessageAt || a.createdAt;
|
||||
const tb = b.lastMessageAt || b.createdAt;
|
||||
return new Date(tb).getTime() - new Date(ta).getTime();
|
||||
});
|
||||
return { messages: { ...state.messages, [message.chatId]: updated }, chats };
|
||||
}),
|
||||
|
||||
updateMessage: (message) => set(state => ({
|
||||
messages: {
|
||||
...state.messages,
|
||||
[message.chatId]: (state.messages[message.chatId] || []).map(m =>
|
||||
m.id === message.id ? message : m
|
||||
)
|
||||
}
|
||||
})),
|
||||
|
||||
removeMessage: (chatId, messageId) => set(state => ({
|
||||
messages: {
|
||||
...state.messages,
|
||||
[chatId]: (state.messages[chatId] || []).map(m =>
|
||||
m.id === messageId ? { ...m, isDeleted: true, content: 'Сообщение удалено' } : m
|
||||
)
|
||||
}
|
||||
})),
|
||||
|
||||
setOnline: (userId, online) => set(state => {
|
||||
const next = new Set(state.onlineUsers);
|
||||
online ? next.add(userId) : next.delete(userId);
|
||||
return { onlineUsers: next };
|
||||
}),
|
||||
|
||||
setTyping: (chatId, userId, typing) => set(state => {
|
||||
const current = state.typingUsers[chatId] || [];
|
||||
const next = typing
|
||||
? [...new Set([...current, userId])]
|
||||
: current.filter(id => id !== userId);
|
||||
return { typingUsers: { ...state.typingUsers, [chatId]: next } };
|
||||
}),
|
||||
|
||||
setConnected: (v) => set({ connected: v }),
|
||||
|
||||
markRead: (chatId) => set(state => ({
|
||||
chats: state.chats.map(c => c.id === chatId ? { ...c, unreadCount: 0 } : c)
|
||||
})),
|
||||
|
||||
logout: () => {
|
||||
localStorage.removeItem('jc_token');
|
||||
localStorage.removeItem('jc_user');
|
||||
set({ user: null, chats: [], activeChat: null, messages: {}, connected: false });
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user