diff --git a/frontend/src/pages/chat/css/left-panel.module.scss b/frontend/src/pages/chat/css/left-panel.module.scss
index 0184cef..0cf9e9c 100644
--- a/frontend/src/pages/chat/css/left-panel.module.scss
+++ b/frontend/src/pages/chat/css/left-panel.module.scss
@@ -74,45 +74,11 @@
}
}
- .chatTabs {
- margin-top: 5px;
- width: 100%;
- height: calc(100% - 80px);
- display: flex;
- flex-direction: column;
- min-height: 0; // prevent flex collapse when inner overflows
- --mdui-color-surface: $color-dark-surface-container;
- --mdui-color-surface-variant: transparent;
-
- img {
- width: 45px;
- height: 45px;
- border-radius: 20%;
- object-fit: cover;
- }
-
- mdui-tabs {
- height: 100%;
- display: flex;
- flex-direction: column;
- min-height: 0; // enable inner panel to scroll
- }
-
- mdui-tab-panel[active] {
- flex: 1;
- display: flex;
- flex-direction: column;
- min-height: 0; // critical to avoid collapsing
- overflow: auto;
- }
-
- mdui-list {
- flex: 1;
- min-height: 0; // allow scroll area to size correctly
- overflow-y: auto;
- padding: 0;
- margin: 0;
- }
+ .unifiedChatsList {
+ flex: 1;
+ min-height: 0; // allow scroll area to size correctly
+ overflow-y: auto;
+ margin-top: 10px;
}
// Search container
diff --git a/frontend/src/pages/chat/ui/left/ChatTabs.tsx b/frontend/src/pages/chat/ui/left/ChatTabs.tsx
deleted file mode 100644
index 6560205..0000000
--- a/frontend/src/pages/chat/ui/left/ChatTabs.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { useAppState, type ChatTabs } from "@/pages/chat/state";
-import { UnifiedChatsList } from "./UnifiedChatsList";
-import { MaterialTab, MaterialTabPanel, MaterialTabs } from "@/utils/material";
-import styles from "@/pages/chat/css/left-panel.module.scss";
-
-export function ChatTabs() {
- const { chat, setActiveTab } = useAppState();
-
- return (
-
- setActiveTab(e.target.value as ChatTabs)}>
-
- Чаты
-
-
- Каналы
-
-
- Контакты
-
-
-
-
-
- Скоро будет...
- Скоро будет...
-
-
- );
-}
diff --git a/frontend/src/pages/chat/ui/left/LeftPanel.tsx b/frontend/src/pages/chat/ui/left/LeftPanel.tsx
index 49cbc8e..7fdce4b 100644
--- a/frontend/src/pages/chat/ui/left/LeftPanel.tsx
+++ b/frontend/src/pages/chat/ui/left/LeftPanel.tsx
@@ -2,7 +2,7 @@ import { useAppState } from "@/pages/chat/state";
import { useState } from "react";
import { SettingsDialog } from "./settings/SettingsDialog";
import { UsernameSearch } from "./UsernameSearch";
-import { ChatTabs } from "./ChatTabs";
+import { UnifiedChatsList } from "./UnifiedChatsList";
import { ChatHeader } from "./ChatHeader";
import { MaterialBottomAppBar, MaterialFab, MaterialIconButton } from "@/utils/material";
import styles from "@/pages/chat/css/left-panel.module.scss";
@@ -35,7 +35,7 @@ export function LeftPanel() {
-
+
);
diff --git a/frontend/src/pages/chat/ui/left/UnifiedChatsList.tsx b/frontend/src/pages/chat/ui/left/UnifiedChatsList.tsx
index 3a4f709..fb40dad 100644
--- a/frontend/src/pages/chat/ui/left/UnifiedChatsList.tsx
+++ b/frontend/src/pages/chat/ui/left/UnifiedChatsList.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect, useCallback } from "react";
+import { useState, useEffect, useCallback, useMemo } from "react";
import { useAppState } from "@/pages/chat/state";
import { useDM, type DMUser } from "@/pages/chat/hooks/useDM";
import { API_BASE_URL } from "@/core/config";
@@ -36,18 +36,17 @@ interface DMConversation {
type ChatItem = PublicChat | DMConversation;
+const PUBLIC_CHAT: PublicChat = {
+ id: "general",
+ name: "Общий чат",
+ type: "public"
+};
+
export function UnifiedChatsList() {
const { user, switchToPublicChat, switchToDM, chat } = useAppState();
const { dmUsers, isLoadingUsers, loadUsers } = useDM();
-
- const [publicChats] = useState([
- { id: "general", name: "Общий чат", type: "public" },
- { id: "general2", name: "Общий чат 2", type: "public" }
- ]);
const [lastMessages, setLastMessages] = useState>({});
- const [allChats, setAllChats] = useState([]);
- // Load public chat last messages
const loadLastMessages = useCallback(async () => {
if (!user.authToken) return;
@@ -58,13 +57,9 @@ export function UnifiedChatsList() {
if (response.ok) {
const data = await response.json();
- if (data.messages && data.messages.length > 0) {
+ if (data.messages?.length > 0) {
const lastMessage = data.messages[data.messages.length - 1];
-
- setLastMessages({
- general: lastMessage,
- general2: lastMessage
- });
+ setLastMessages({ general: lastMessage });
}
}
} catch (error) {
@@ -72,7 +67,6 @@ export function UnifiedChatsList() {
}
}, [user.authToken]);
- // Load DM users when chats tab is active
useEffect(() => {
if (chat.activeTab === "chats") {
loadUsers();
@@ -80,79 +74,56 @@ export function UnifiedChatsList() {
}
}, [chat.activeTab, loadUsers, loadLastMessages]);
- // Combine public chats and DMs into one list
- useEffect(() => {
- const publicChatItems: ChatItem[] = publicChats.map(chat => ({
- ...chat,
- lastMessage: lastMessages[chat.id]
- }));
+ const allChats = useMemo(() => {
+ return [
+ ...dmUsers.map((user: DMUser) => ({
+ ...user,
+ userId: user.id,
+ type: "dm" as const
+ })),
+ {
+ ...PUBLIC_CHAT,
+ lastMessage: lastMessages[PUBLIC_CHAT.id]
+ }
+ ];
+ }, [lastMessages, dmUsers]);
- const dmChatItems: ChatItem[] = dmUsers.map((user: DMUser) => ({
- id: user.id,
- userId: user.id, // Add userId field
- username: user.username,
- display_name: user.display_name,
- profile_picture: user.profile_picture,
- online: user.online,
- type: "dm" as const,
- lastMessage: user.lastMessage,
- unreadCount: user.unreadCount,
- publicKey: user.publicKey
- }));
-
- // Combine and sort by last message timestamp (DMs first, then public chats)
- const combined = [...dmChatItems, ...publicChatItems];
- setAllChats(combined);
- }, [publicChats, lastMessages, dmUsers]);
-
- // WebSocket listener for public chat message updates
useEffect(() => {
if (!websocket) return;
- const handleWebSocketMessage = (e: MessageEvent) => {
+ function handleWebSocketMessage(e: MessageEvent) {
try {
const msg = JSON.parse(e.data);
if (msg.type === "newMessage") {
const newMessage = msg.data as Message;
- // Update all public chats with the new message
- setLastMessages(prev => {
- const updated = { ...prev };
- publicChats.forEach(chat => {
- updated[chat.id] = newMessage;
- });
- return updated;
- });
+ setLastMessages(prev => ({
+ ...prev,
+ [PUBLIC_CHAT.id]: newMessage
+ }));
} else if (msg.type === "messageEdited") {
const editedMessage = msg.data as Message;
- // Update only if the edited message is the current last message
setLastMessages(prev => {
- const updated = { ...prev };
- publicChats.forEach(chat => {
- if (updated[chat.id]?.id === editedMessage.id) {
- updated[chat.id] = editedMessage;
- }
- });
- return updated;
+ if (prev[PUBLIC_CHAT.id]?.id === editedMessage.id) {
+ return {
+ ...prev,
+ [PUBLIC_CHAT.id]: editedMessage
+ };
+ }
+ return prev;
});
} else if (msg.type === "messageDeleted") {
const deletedMessageId = msg.data?.message_id;
- let needsReload = false;
-
setLastMessages(prev => {
- const updated = { ...prev };
- publicChats.forEach(chat => {
- if (updated[chat.id]?.id === deletedMessageId) {
- updated[chat.id] = undefined;
- needsReload = true;
- }
- });
- return updated;
+ if (prev[PUBLIC_CHAT.id]?.id === deletedMessageId) {
+ loadLastMessages();
+ return {
+ ...prev,
+ [PUBLIC_CHAT.id]: undefined
+ };
+ }
+ return prev;
});
-
- if (needsReload) {
- loadLastMessages();
- }
}
} catch (error) {
console.error("Failed to handle WebSocket message in UnifiedChatsList:", error);
@@ -161,45 +132,33 @@ export function UnifiedChatsList() {
websocket.addEventListener("message", handleWebSocketMessage);
return () => websocket.removeEventListener("message", handleWebSocketMessage);
- }, [publicChats, loadLastMessages]);
+ }, [loadLastMessages]);
- // Subscribe to online status for all DM users
useEffect(() => {
- const dmUsers = allChats.filter(chat => chat.type === "dm") as DMConversation[];
-
- // Subscribe to all DM users
dmUsers.forEach(dmUser => {
onlineStatusManager.subscribe(dmUser.id);
});
- // Cleanup function to unsubscribe from all users
return () => {
dmUsers.forEach(dmUser => {
onlineStatusManager.unsubscribe(dmUser.id);
});
};
- }, [allChats]);
+ }, [dmUsers]);
function formatPublicChatMessage(chatId: string): string {
const lastMessage = lastMessages[chatId];
- if (!lastMessage) {
- return "";
- }
+ if (!lastMessage) return "";
const isCurrentUser = lastMessage.user_id === user.currentUser?.id;
const prefix = isCurrentUser ? "Вы: " : `${lastMessage.username}: `;
-
- const maxContentLength = 50 - prefix.length;
- const content = lastMessage.content.length > maxContentLength
- ? lastMessage.content.substring(0, maxContentLength) + "..."
+ const maxLength = 50 - prefix.length;
+ const content = lastMessage.content.length > maxLength
+ ? lastMessage.content.substring(0, maxLength) + "..."
: lastMessage.content;
return prefix + content;
- }
-
- async function handlePublicChatClick(chatName: string) {
- await switchToPublicChat(chatName);
- }
+ };
async function handleDMClick(dmConversation: DMConversation) {
if (!dmConversation.publicKey) {
@@ -207,12 +166,11 @@ export function UnifiedChatsList() {
if (!authToken) return;
const publicKey = await fetchUserPublicKey(dmConversation.id, authToken);
- if (publicKey) {
- dmConversation.publicKey = publicKey;
- } else {
+ if (!publicKey) {
console.error("Failed to get public key for user:", dmConversation.id);
return;
}
+ dmConversation.publicKey = publicKey;
}
await switchToDM({
@@ -222,26 +180,27 @@ export function UnifiedChatsList() {
profilePicture: dmConversation.profile_picture,
online: dmConversation.online || false
});
- }
+ };
if (isLoadingUsers) {
return ;
}
return (
-
+
{allChats.map((chat) => {
if (chat.type === "public") {
+ const formattedMessage = formatPublicChatMessage(chat.id);
return (
handlePublicChatClick(chat.name)}
+ onClick={() => switchToPublicChat(chat.name)}
style={{ cursor: "pointer" }}
>
- {formatPublicChatMessage(chat.id) && (
+ {formattedMessage && (
- {formatPublicChatMessage(chat.id)}
+ {formattedMessage}
)}
);
- } else {
- return (
- handleDMClick(chat)}
- style={{ cursor: "pointer" }}
- >
-
- {chat.display_name}
-
-
-
- {chat.lastMessage || "Нет сообщений"}
-
-
-

{
- e.target.src = defaultAvatar;
- }}
- />
-
-
- {chat.unreadCount > 0 && (
-
- {chat.unreadCount}
-
- )}
-
- );
}
+
+ return (
+ handleDMClick(chat)}
+ style={{ cursor: "pointer" }}
+ >
+
+ {chat.display_name}
+
+
+
+ {chat.lastMessage || "Нет сообщений"}
+
+
+

{
+ e.target.src = defaultAvatar;
+ }}
+ />
+
+
+ {chat.unreadCount > 0 && (
+
+ {chat.unreadCount}
+
+ )}
+
+ );
})}
);