fixed code

This commit is contained in:
SlimusMinus
2026-08-11 00:24:07 +03:00
parent c50b75eece
commit 7f59ee1f38
95 changed files with 3623 additions and 2830 deletions

View File

@@ -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>
);
}
}