Start DMs

This commit is contained in:
2025-09-05 21:41:48 +03:00
Unverified
parent 420bad2405
commit 04d3e4d995
10 changed files with 691 additions and 9 deletions
+128
View File
@@ -0,0 +1,128 @@
import { useState, useEffect, useRef } from "react";
import { useAppState } from "../../state";
import { useDM } from "../../../hooks/useDM";
import { ChatMessages } from "./ChatMessages";
import defaultAvatar from "../../../resources/images/default-avatar.png";
export function DMPanel() {
const { chat } = useAppState();
const { sendDMMessage, isLoadingHistory } = useDM();
const [message, setMessage] = useState("");
const messagesEndRef = useRef<HTMLDivElement>(null);
const activeDm = chat.activeDm;
// Scroll to bottom when messages change
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [chat.messages]);
const handleSendMessage = async (e: React.FormEvent) => {
e.preventDefault();
if (!message.trim() || !activeDm?.publicKey) return;
try {
await sendDMMessage(activeDm.userId, activeDm.publicKey, message);
setMessage("");
} catch (error) {
console.error("Failed to send DM:", error);
}
};
const handleProfileClick = () => {
// TODO: Implement profile dialog for DM user
console.log("Profile clicked for DM user:", activeDm?.username);
};
if (!activeDm) {
return (
<div className="chat-main" id="chat-inner">
<div className="chat-header">
<img src={defaultAvatar} alt="Avatar" className="chat-header-avatar" />
<div className="chat-header-info">
<div className="info-chat">
<h4 id="chat-name">Выберите пользователя</h4>
<p>
<span className="online-status"></span>
Выберите пользователя для начала разговора
</p>
</div>
</div>
</div>
<div className="chat-messages" id="chat-messages">
<div style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100%",
color: "var(--mdui-color-on-surface-variant)"
}}>
Выберите пользователя из списка для начала личных сообщений
</div>
</div>
</div>
);
}
return (
<div className="chat-main" id="chat-inner">
<div className="chat-header">
<img
src={defaultAvatar}
alt="Avatar"
className="chat-header-avatar"
onClick={handleProfileClick}
style={{ cursor: "pointer" }}
/>
<div className="chat-header-info">
<div className="info-chat">
<h4 id="chat-name">{activeDm.username}</h4>
<p>
<span className="online-status"></span>
Личные сообщения
</p>
</div>
<a href="#" id="hide-chat">Свернуть чат</a>
</div>
</div>
<div className="chat-messages" id="chat-messages">
{isLoadingHistory ? (
<div style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100%",
color: "var(--mdui-color-on-surface-variant)"
}}>
Загрузка сообщений...
</div>
) : (
<>
<ChatMessages />
<div ref={messagesEndRef} />
</>
)}
</div>
<div className="chat-input-wrapper">
<div className="chat-input">
<form className="input-group" id="message-form" onSubmit={handleSendMessage}>
<input
type="text"
className="message-input"
id="message-input"
placeholder="Напишите сообщение..."
autoComplete="off"
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<button type="submit" className="send-btn">
<span className="material-symbols filled">send</span>
</button>
</form>
</div>
</div>
</div>
);
}
@@ -0,0 +1,69 @@
import { useEffect } from "react";
import { useDM } from "../../../hooks/useDM";
import { useAppState } from "../../state";
import defaultAvatar from "../../../resources/images/default-avatar.png";
export function DMUsersList() {
const { dmUsers, isLoadingUsers, loadUsers, startDMConversation } = useDM();
const { chat } = useAppState();
useEffect(() => {
if (chat.activeTab === "dms") {
loadUsers();
}
}, [chat.activeTab, loadUsers]);
if (isLoadingUsers) {
return (
<mdui-list>
<mdui-list-item headline="Загрузка..." description="Получение списка пользователей...">
<img src={defaultAvatar} alt="" slot="icon" />
</mdui-list-item>
</mdui-list>
);
}
if (dmUsers.length === 0) {
return (
<mdui-list>
<mdui-list-item headline="Нет пользователей" description="Пользователи не найдены">
<img src={defaultAvatar} alt="" slot="icon" />
</mdui-list-item>
</mdui-list>
);
}
return (
<mdui-list>
{dmUsers.map((user) => (
<mdui-list-item
key={user.id}
headline={user.username}
description={user.lastMessage || "Нет сообщений"}
onClick={() => startDMConversation(user)}
style={{ cursor: "pointer" }}
>
<img
src={user.profile_picture || defaultAvatar}
alt={user.username}
slot="icon"
style={{
width: "40px",
height: "40px",
borderRadius: "50%",
objectFit: "cover"
}}
onError={(e) => {
(e.target as HTMLImageElement).src = defaultAvatar;
}}
/>
{user.unreadCount > 0 && (
<mdui-badge slot="end-icon">
{user.unreadCount}
</mdui-badge>
)}
</mdui-list-item>
))}
</mdui-list>
);
}
+10 -3
View File
@@ -2,9 +2,12 @@ import { PRODUCT_NAME } from "../../../core/config";
import { useDialog } from "../../contexts/DialogContext";
import { useChat } from "../../hooks/useChat";
import defaultAvatar from "../../../resources/images/default-avatar.png";
import { useState } from "react";
import { useState, type FormEvent } from "react";
import { ProfileDialog } from "../profile/ProfileDialog";
import { SettingsDialog } from "../settings/SettingsDialog";
import { DMUsersList } from "./DMUsersList";
import type { Tabs } from "mdui";
import type { ChatTabs } from "../../state";
function BottomAppBar() {
const [settingsOpen, onSettingsOpenChange] = useState(false);
@@ -30,9 +33,13 @@ function ChatTabs() {
setCurrentChat(chatName);
};
const handleTabChange = (e: FormEvent<Tabs>) => {
setActiveTab((e.target as Tabs).value as ChatTabs);
};
return (
<div className="chat-tabs">
<mdui-tabs value={activeTab} full-width onChange={(e: Event & any) => setActiveTab(e.value)}>
<mdui-tabs value={activeTab} full-width onChange={handleTabChange}>
<mdui-tab value="chats">Чаты</mdui-tab>
<mdui-tab value="channels">Каналы</mdui-tab>
<mdui-tab value="contacts">Контакты</mdui-tab>
@@ -63,7 +70,7 @@ function ChatTabs() {
<mdui-tab-panel slot="panel" value="channels">Скоро будет...</mdui-tab-panel>
<mdui-tab-panel slot="panel" value="contacts">Скоро будет...</mdui-tab-panel>
<mdui-tab-panel slot="panel" value="dms">
<mdui-list id="dm-users"></mdui-list>
<DMUsersList />
</mdui-tab-panel>
</mdui-tabs>
</div>
@@ -30,8 +30,6 @@ export function MessageContextMenu({
isOpen,
onOpenChange
}: MessageContextMenuProps) {
console.log("MessageContextMenu rendered with position:", position, "message:", message.id);
// Internal state for dialogs and closing animation
const [editDialogOpen, setEditDialogOpen] = useState(false);
const [replyDialogOpen, setReplyDialogOpen] = useState(false);
+15 -2
View File
@@ -1,12 +1,15 @@
import { useEffect, useState } from "react";
import { useChat } from "../../hooks/useChat";
import { useAppState } from "../../state";
import { ChatInputWrapper } from "./ChatInputWrapper";
import { ChatMainHeader } from "./ChatMainHeader";
import { ChatMessages } from "./ChatMessages";
import { DMPanel } from "./DMPanel";
import { delay } from "../../../utils/utils";
export function RightPanel() {
const { isChatSwitching } = useChat();
const { chat } = useAppState();
const [switchIn, setSwitchIn] = useState(false);
const [switchOut, setSwitchOut] = useState(false);
@@ -23,13 +26,23 @@ export function RightPanel() {
})();
}, [isChatSwitching])
return (
<div className={`chat-container ${switchIn ? "chat-switch-in" : ""} ${switchOut ? "chat-switch-out" : ""}`}>
let content: React.ReactNode;
if (chat.activeTab === "dms") {
content = <DMPanel />
} else {
content = (
<div className="chat-main" id="chat-inner">
<ChatMainHeader />
<ChatMessages />
<ChatInputWrapper />
</div>
)
}
return (
<div className={`chat-container ${switchIn ? "chat-switch-in" : ""} ${switchOut ? "chat-switch-out" : ""}`}>
{content}
</div>
);
}