mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
Implement chat panels
This commit is contained in:
@@ -1,14 +1,22 @@
|
||||
import { useState } from "react";
|
||||
import { useChat } from "../../hooks/useChat";
|
||||
|
||||
export function ChatInputWrapper() {
|
||||
interface ChatInputWrapperProps {
|
||||
onSendMessage?: (message: string) => void;
|
||||
}
|
||||
|
||||
export function ChatInputWrapper({ onSendMessage }: ChatInputWrapperProps) {
|
||||
const [message, setMessage] = useState("");
|
||||
const { sendMessage } = useChat();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (message.trim()) {
|
||||
await sendMessage(message);
|
||||
if (onSendMessage) {
|
||||
onSendMessage(message);
|
||||
} else {
|
||||
await sendMessage(message);
|
||||
}
|
||||
setMessage("");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,9 +10,16 @@ import { useState } from "react";
|
||||
import { delay } from "../../../utils/utils";
|
||||
import { request } from "../../../websocket";
|
||||
|
||||
export function ChatMessages() {
|
||||
const { messages } = useChat();
|
||||
interface ChatMessagesProps {
|
||||
messages?: MessageType[];
|
||||
}
|
||||
|
||||
export function ChatMessages({ messages: propMessages }: ChatMessagesProps) {
|
||||
const { messages: hookMessages } = useChat();
|
||||
const { user } = useAppState();
|
||||
|
||||
// Use prop messages if provided, otherwise use hook messages
|
||||
const messages = propMessages || hookMessages;
|
||||
const [profileDialogOpen, setProfileDialogOpen] = useState(false);
|
||||
const [selectedUserProfile, setSelectedUserProfile] = useState<UserProfile | null>(null);
|
||||
const [isLoadingProfile, setIsLoadingProfile] = useState(false);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useEffect } from "react";
|
||||
import { useDM } from "../../../hooks/useDM";
|
||||
import { useAppState } from "../../state";
|
||||
import { fetchUserPublicKey } from "../../../api/dmApi";
|
||||
import defaultAvatar from "../../../resources/images/default-avatar.png";
|
||||
|
||||
export function DMUsersList() {
|
||||
const { dmUsers, isLoadingUsers, loadUsers, startDMConversation } = useDM();
|
||||
const { chat } = useAppState();
|
||||
const { dmUsers, isLoadingUsers, loadUsers } = useDM();
|
||||
const { chat, switchToDM } = useAppState();
|
||||
|
||||
useEffect(() => {
|
||||
if (chat.activeTab === "dms") {
|
||||
@@ -33,6 +34,33 @@ export function DMUsersList() {
|
||||
);
|
||||
}
|
||||
|
||||
const handleUserClick = async (user: any) => {
|
||||
if (!user.publicKey) {
|
||||
// Get public key if not already loaded
|
||||
const authToken = useAppState.getState().user.authToken;
|
||||
if (!authToken) {
|
||||
console.error("No auth token available");
|
||||
return;
|
||||
}
|
||||
|
||||
const publicKey = await fetchUserPublicKey(user.id, authToken);
|
||||
if (publicKey) {
|
||||
user.publicKey = publicKey;
|
||||
} else {
|
||||
console.error("Failed to get public key for user:", user.id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await switchToDM({
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
publicKey: user.publicKey,
|
||||
profilePicture: user.profile_picture,
|
||||
online: user.online || false
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<mdui-list>
|
||||
{dmUsers.map((user) => (
|
||||
@@ -40,7 +68,7 @@ export function DMUsersList() {
|
||||
key={user.id}
|
||||
headline={user.username}
|
||||
description={user.lastMessage || "Нет сообщений"}
|
||||
onClick={() => startDMConversation(user)}
|
||||
onClick={() => handleUserClick(user)}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<img
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { PRODUCT_NAME } from "../../../core/config";
|
||||
import { useDialog } from "../../contexts/DialogContext";
|
||||
import { useChat } from "../../hooks/useChat";
|
||||
import { useAppState } from "../../state";
|
||||
import defaultAvatar from "../../../resources/images/default-avatar.png";
|
||||
import { useState, type FormEvent } from "react";
|
||||
import { ProfileDialog } from "../profile/ProfileDialog";
|
||||
@@ -27,14 +28,16 @@ function BottomAppBar() {
|
||||
|
||||
|
||||
function ChatTabs() {
|
||||
const { activeTab, setActiveTab, setCurrentChat } = useChat();
|
||||
const { chat, switchToTab, switchToPublicChat } = useAppState();
|
||||
const { activeTab } = chat;
|
||||
|
||||
const handleChatClick = (chatName: string) => {
|
||||
setCurrentChat(chatName);
|
||||
const handleChatClick = async (chatName: string) => {
|
||||
await switchToPublicChat(chatName);
|
||||
};
|
||||
|
||||
const handleTabChange = (e: FormEvent<Tabs>) => {
|
||||
setActiveTab((e.target as Tabs).value as ChatTabs);
|
||||
const handleTabChange = async (e: FormEvent<Tabs>) => {
|
||||
const tab = (e.target as Tabs).value as ChatTabs;
|
||||
await switchToTab(tab);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { MessagePanel, type MessagePanelState } from "../../panels/MessagePanel";
|
||||
import { ChatMainHeader } from "./ChatMainHeader";
|
||||
import { ChatMessages } from "./ChatMessages";
|
||||
import { ChatInputWrapper } from "./ChatInputWrapper";
|
||||
import defaultAvatar from "../../../resources/images/default-avatar.png";
|
||||
|
||||
interface MessagePanelRendererProps {
|
||||
panel: MessagePanel | null;
|
||||
isChatSwitching: boolean;
|
||||
}
|
||||
|
||||
export function MessagePanelRenderer({ panel, isChatSwitching }: MessagePanelRendererProps) {
|
||||
const [panelState, setPanelState] = useState<MessagePanelState | null>(null);
|
||||
const [switchIn, setSwitchIn] = useState(false);
|
||||
const [switchOut, setSwitchOut] = useState(false);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Handle panel state changes
|
||||
useEffect(() => {
|
||||
if (panel) {
|
||||
setPanelState(panel.getState());
|
||||
|
||||
// Set up state change listener
|
||||
const handleStateChange = (newState: MessagePanelState) => {
|
||||
setPanelState(newState);
|
||||
};
|
||||
|
||||
// Store the handler for cleanup
|
||||
(panel as any).onStateChange = handleStateChange;
|
||||
} else {
|
||||
setPanelState(null);
|
||||
}
|
||||
}, [panel]);
|
||||
|
||||
// Handle chat switching animation
|
||||
useEffect(() => {
|
||||
if (isChatSwitching) {
|
||||
setSwitchOut(true);
|
||||
setTimeout(() => {
|
||||
setSwitchOut(false);
|
||||
setSwitchIn(true);
|
||||
setTimeout(() => setSwitchIn(false), 200);
|
||||
}, 250);
|
||||
}
|
||||
}, [isChatSwitching]);
|
||||
|
||||
// Scroll to bottom when messages change
|
||||
useEffect(() => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
}, [panelState?.messages]);
|
||||
|
||||
if (!panel || !panelState) {
|
||||
return (
|
||||
<div className="chat-container">
|
||||
<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">Select a chat</h4>
|
||||
<p>
|
||||
<span className="online-status"></span>
|
||||
Choose a chat to start messaging
|
||||
</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)"
|
||||
}}>
|
||||
Select a chat from the sidebar to start messaging
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`chat-container ${switchIn ? "chat-switch-in" : ""} ${switchOut ? "chat-switch-out" : ""}`}>
|
||||
<div className="chat-main" id="chat-inner">
|
||||
<div className="chat-header">
|
||||
<img
|
||||
src={panelState.profilePicture || defaultAvatar}
|
||||
alt="Avatar"
|
||||
className="chat-header-avatar"
|
||||
onClick={panel.handleProfileClick}
|
||||
style={{ cursor: "pointer" }}
|
||||
/>
|
||||
<div className="chat-header-info">
|
||||
<div className="info-chat">
|
||||
<h4 id="chat-name">{panelState.title}</h4>
|
||||
<p>
|
||||
<span className={`online-status ${panelState.online ? "online" : "offline"}`}></span>
|
||||
{panelState.online ? "Online" : "Offline"}
|
||||
{panelState.isTyping && " • Typing..."}
|
||||
</p>
|
||||
</div>
|
||||
<a href="#" id="hide-chat">Свернуть чат</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="chat-messages" id="chat-messages">
|
||||
{panelState.isLoading ? (
|
||||
<div style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
color: "var(--mdui-color-on-surface-variant)"
|
||||
}}>
|
||||
Loading messages...
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<ChatMessages messages={panelState.messages} />
|
||||
<div ref={messagesEndRef} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ChatInputWrapper onSendMessage={panel.handleSendMessage} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,48 +1,13 @@
|
||||
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";
|
||||
import { MessagePanelRenderer } from "./MessagePanelRenderer";
|
||||
|
||||
export function RightPanel() {
|
||||
const { isChatSwitching } = useChat();
|
||||
const { chat } = useAppState();
|
||||
const [switchIn, setSwitchIn] = useState(false);
|
||||
const [switchOut, setSwitchOut] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (isChatSwitching) {
|
||||
setSwitchOut(true);
|
||||
} else {
|
||||
setSwitchIn(true);
|
||||
await delay(200);
|
||||
setSwitchIn(false);
|
||||
setSwitchOut(false);
|
||||
}
|
||||
})();
|
||||
}, [isChatSwitching])
|
||||
|
||||
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>
|
||||
<MessagePanelRenderer
|
||||
panel={chat.activePanel}
|
||||
isChatSwitching={chat.isChatSwitching}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user