mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
Start DMs
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user