fixed code
This commit is contained in:
@@ -1,14 +1,17 @@
|
||||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useChats } from "../hooks/useChats";
|
||||
import { useChatSocket } from "../hooks/useChatSocket";
|
||||
import { getPostForChat } from "../stores/chatPostStore";
|
||||
import { setActiveChatId } from "../stores/activeChatStore";
|
||||
import { useUnread } from "../context/UnreadContext";
|
||||
import { useAuthContext } from "../context/AuthContext";
|
||||
import { getImageUrl } from "../config/authConstants";
|
||||
import Picker from "@emoji-mart/react";
|
||||
import emojiData from "@emoji-mart/data";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ChatHeader from "../components/chat/ChatHeader";
|
||||
import MessageBubble from "../components/chat/MessageBubble";
|
||||
import ChatInputBar from "../components/chat/ChatInputBar";
|
||||
import Toast from "../components/Toast";
|
||||
|
||||
export default function ChatPage() {
|
||||
const { t } = useTranslation();
|
||||
@@ -16,135 +19,125 @@ export default function ChatPage() {
|
||||
const navigate = useNavigate();
|
||||
const { fetchMessages, markAsRead } = useChats();
|
||||
const { clearChatUnread } = useUnread();
|
||||
const { user } = useAuthContext();
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [input, setInput] = useState("");
|
||||
const [showEmoji, setShowEmoji] = useState(false);
|
||||
const [loadError, setLoadError] = useState("");
|
||||
const [sendError, setSendError] = useState("");
|
||||
const messagesEndRef = useRef(null);
|
||||
const emojiPickerRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClick = (e) => {
|
||||
if (emojiPickerRef.current && !emojiPickerRef.current.contains(e.target)) {
|
||||
setShowEmoji(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handleClick);
|
||||
return () => document.removeEventListener("mousedown", handleClick);
|
||||
const myUserId = user?.id;
|
||||
|
||||
/** Мержит новое сообщение по id — защита от дублей (REST + WS эхо). */
|
||||
const addMessage = useCallback((msg) => {
|
||||
setMessages((prev) => {
|
||||
if (msg.id != null && prev.some((m) => m.id === msg.id)) return prev;
|
||||
return [...prev, msg];
|
||||
});
|
||||
}, []);
|
||||
|
||||
const myUserId = (() => {
|
||||
try { return JSON.parse(localStorage.getItem("user"))?.id; }
|
||||
catch { return null; }
|
||||
})();
|
||||
|
||||
const handleIncoming = useCallback((msg) => {
|
||||
setMessages((prev) => [...prev, msg]);
|
||||
}, []);
|
||||
addMessage(msg);
|
||||
}, [addMessage]);
|
||||
|
||||
const { sendMessage, connected } = useChatSocket(chatId, handleIncoming);
|
||||
|
||||
useEffect(() => {
|
||||
if (chatId) {
|
||||
setActiveChatId(chatId);
|
||||
fetchMessages(chatId).then(setMessages);
|
||||
markAsRead(chatId);
|
||||
clearChatUnread(chatId);
|
||||
}
|
||||
if (!chatId) return;
|
||||
|
||||
setActiveChatId(chatId);
|
||||
setLoadError("");
|
||||
|
||||
fetchMessages(chatId)
|
||||
.then((list) => {
|
||||
// Мержим историю с уже пришедшими по сокету сообщениями,
|
||||
// а не затираем их — иначе можно потерять сообщение, пришедшее
|
||||
// по WS, пока REST-запрос ещё letел.
|
||||
setMessages((prev) => {
|
||||
const byId = new Map(list.map((m) => [m.id, m]));
|
||||
const extra = prev.filter((m) => !byId.has(m.id));
|
||||
return [...list, ...extra];
|
||||
});
|
||||
})
|
||||
.catch(() => setLoadError(t('chat.loadError')));
|
||||
|
||||
markAsRead(chatId);
|
||||
clearChatUnread(chatId);
|
||||
|
||||
return () => setActiveChatId(null);
|
||||
}, [chatId, fetchMessages, markAsRead, clearChatUnread]);
|
||||
}, [chatId, fetchMessages, markAsRead, clearChatUnread, t]);
|
||||
|
||||
useEffect(() => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
}, [messages]);
|
||||
|
||||
const handleSend = (e) => {
|
||||
const handleSend = useCallback((e) => {
|
||||
e.preventDefault();
|
||||
if (!input.trim()) return;
|
||||
sendMessage(input);
|
||||
setInput("");
|
||||
};
|
||||
const trimmed = input.trim();
|
||||
if (!trimmed) return;
|
||||
|
||||
const handleEmojiSelect = (emoji) => {
|
||||
const sent = sendMessage(trimmed);
|
||||
if (!sent) {
|
||||
setSendError(t('chat.sendError'));
|
||||
return;
|
||||
}
|
||||
setInput("");
|
||||
}, [input, sendMessage, t]);
|
||||
|
||||
const handleEmojiSelect = useCallback((emoji) => {
|
||||
setInput((prev) => prev + emoji.native);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const post = getPostForChat(chatId);
|
||||
const postImg = getImageUrl(post?.images?.[0]);
|
||||
|
||||
return (
|
||||
<div className="f-app">
|
||||
<div className="chat-page">
|
||||
<div className="chat-header">
|
||||
<button className="chat-back-btn" onClick={() => navigate("/chats")}>
|
||||
←
|
||||
</button>
|
||||
<div className="chat-header-avatar">
|
||||
{postImg ? (
|
||||
<img src={postImg} alt="" className="chat-header-avatar-img" />
|
||||
) : (
|
||||
post?.emoji || "📋"
|
||||
<div className="f-app">
|
||||
<div className="chat-page">
|
||||
<ChatHeader
|
||||
title={post?.title || t('chat.title')}
|
||||
onBack={() => navigate("/chats")}
|
||||
avatar={postImg ? (
|
||||
<img src={postImg} alt="" className="chat-header-avatar-img" />
|
||||
) : (
|
||||
post?.emoji || "📋"
|
||||
)}
|
||||
statusLabel={connected ? t('chat.online') : t('chat.offline')}
|
||||
statusConnected={connected}
|
||||
/>
|
||||
|
||||
<div className="chat-messages">
|
||||
{loadError && <div className="chat-empty">{loadError}</div>}
|
||||
{!loadError && messages.length === 0 && (
|
||||
<div className="chat-empty">{t('chat.noMessages')}</div>
|
||||
)}
|
||||
{messages.map((m) => (
|
||||
<MessageBubble key={m.id} message={m} isMine={m.senderId === myUserId} />
|
||||
))}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
<div className="chat-header-title">{post?.title || t('chat.title')}</div>
|
||||
<div className={`chat-status${connected ? " connected" : ""}`}>
|
||||
{connected ? t('chat.online') : t('chat.offline')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="chat-messages">
|
||||
{messages.length === 0 && (
|
||||
<div className="chat-empty">{t('chat.noMessages')}</div>
|
||||
)}
|
||||
{messages.map((m) => (
|
||||
<div
|
||||
key={m.id}
|
||||
className={`chat-bubble${m.senderId === myUserId ? " mine" : ""}`}
|
||||
>
|
||||
{m.content}
|
||||
</div>
|
||||
))}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
<div className="chat-input-area">
|
||||
{showEmoji && (
|
||||
<div className="chat-emoji-picker" ref={emojiPickerRef}>
|
||||
<Picker
|
||||
data={emojiData}
|
||||
onEmojiSelect={handleEmojiSelect}
|
||||
theme="dark"
|
||||
previewPosition="none"
|
||||
skinTonePosition="none"
|
||||
searchPosition="sticky"
|
||||
maxSearchResults={30}
|
||||
perLine={8}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<form onSubmit={handleSend} className="chat-input-row">
|
||||
<input
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
<ChatInputBar
|
||||
input={input}
|
||||
onInputChange={setInput}
|
||||
onSend={handleSend}
|
||||
onEmojiSelect={handleEmojiSelect}
|
||||
showEmoji={showEmoji}
|
||||
onToggleEmoji={() => setShowEmoji((v) => !v)}
|
||||
onCloseEmoji={() => setShowEmoji(false)}
|
||||
connected={connected}
|
||||
placeholder={t('chat.messagePlaceholder')}
|
||||
className="chat-input"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className={`chat-emoji-btn${showEmoji ? " active" : ""}`}
|
||||
onClick={() => setShowEmoji((v) => !v)}
|
||||
>
|
||||
😊
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="chat-send-btn"
|
||||
disabled={!connected}
|
||||
>
|
||||
➤
|
||||
</button>
|
||||
</form>
|
||||
/>
|
||||
</div>
|
||||
|
||||
{sendError && (
|
||||
<Toast
|
||||
message={sendError}
|
||||
type="error"
|
||||
onDismiss={() => setSendError("")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user