Добавил дизайн в чате

This commit is contained in:
2025-09-02 15:59:27 +03:00
parent 950f29cea6
commit f77d19975e
2 changed files with 207 additions and 30 deletions

View File

@@ -471,6 +471,98 @@ app.get('/api/messages/:contactId', authenticate, async (req, res) => {
}
});
app.get('/api/messages/:contactId', authenticate, async (req, res) => {
const userId = req.user.id;
const contactId = parseInt(req.params.contactId, 10);
try {
// Ensure table exists
await virtualWorldPool.query(`
CREATE TABLE IF NOT EXISTS messages (
id SERIAL PRIMARY KEY,
sender_id INT NOT NULL,
receiver_id INT NOT NULL,
message TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
is_read BOOLEAN NOT NULL DEFAULT FALSE
)`);
} catch (e) {
console.warn('[GET /api/messages/:contactId] ensure table failed:', e.message);
}
try {
const sql = `SELECT * FROM messages
WHERE (sender_id = $1 AND receiver_id = $2)
OR (sender_id = $2 AND receiver_id = $1)
ORDER BY created_at ASC`;
const messagesRes = await virtualWorldPool.query(sql, [userId, contactId]);
res.json(messagesRes.rows);
} catch (err) {
console.error('[GET /api/messages/:contactId] error:', err);
res.status(500).json({ error: 'Ошибка получения сообщений' });
}
});
app.get('/api/messages-read/:contactId', authenticate, async (req, res) => {
const userId = req.user.id;
const contactId = parseInt(req.params.contactId, 10);
try {
// Проверяем есть ли НЕпрочитанные сообщения от этого контакта
const sql = `SELECT EXISTS (
SELECT 1 FROM messages
WHERE sender_id = $1
AND receiver_id = $2
AND is_read = false
AND sender_id != receiver_id
) as has_unread`;
const result = await virtualWorldPool.query(sql, [contactId, userId]);
// Если есть непрочитанные - возвращаем "true", иначе "false"
const hasUnread = result.rows[0].has_unread;
res.json(hasUnread ? "true" : "false");
} catch (err) {
console.error('[GET /api/messages-read/:contactId] error:', err);
res.status(500).json({ error: 'Ошибка проверки состояния' });
}
});
app.post('/api/messages-read-true-false', authenticate, async (req, res) => {
try {
const userId = req.user.id;
const { contactId } = req.body;
if (!contactId) {
return res.status(400).json({ error: 'contactId required' });
}
// Проверяем что contactId не равен ID текущего пользователя
if (parseInt(contactId) === userId) {
return res.json({ success: true, skipped: 'Нельзя отмечать свои собственные сообщения' });
}
// Отмечаем сообщения как прочитанные ТОЛЬКО если отправитель ≠ получатель
await virtualWorldPool.query(
`UPDATE messages
SET is_read = true
WHERE sender_id = $1 AND receiver_id = $2
AND sender_id != receiver_id`, // Добавляем проверку
[contactId, userId]
);
res.json({ success: true });
} catch (error) {
console.error('Error updating read status:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
app.post('/api/messages/send', authenticate, async (req, res) => {
const senderId = req.user.id;
const { receiverId, message } = req.body || {};

View File

@@ -1364,6 +1364,7 @@ function Game({ avatarUrl, gender }) {
const [newMessage, setNewMessage] = useState("");
const [messageInterval, setMessageInterval] = useState(null);
const [messages, setMessages] = useState([]);
//const [readmes, setReadmes] = useState('false');
const [userProfile, setUserProfile] = useState(null);
// Функция загрузки сообщений
@@ -1371,7 +1372,29 @@ function Game({ avatarUrl, gender }) {
if (!contactId) return;
const token = localStorage.getItem('token');
async function markMessagesAsRead(contactId, token) {
try {
const res = await fetch('/api/messages-read-true-false', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
contactId: contactId
})
});
if (!res.ok) {
console.error('Ошибка отметки сообщений как прочитанных');
}
} catch (error) {
console.error('Error marking as read:', error);
}
}
try {
// 1. Загружаем сообщения
const res = await fetch(`/api/messages/${contactId}`, {
headers: { Authorization: `Bearer ${token}` }
});
@@ -1379,21 +1402,51 @@ function Game({ avatarUrl, gender }) {
if (res.ok) {
const data = await res.json();
setMessages(data);
console.log('Сообщение загружено');
console.log('Сообщения загружены');
// 2. Отмечаем сообщения как прочитанные
await markMessagesAsRead(contactId, token);
// Прокручиваем чат вниз
setTimeout(() => {
const chatContainer = document.getElementById('chatContainer');
if (chatContainer) {
chatContainer.scrollTop = chatContainer.scrollHeight;
}
}, 100);
}, 1000);
} else {
console.error('Ошибка загрузки сообщений');
}
} catch (err) {
console.error('Ошибка сети:', err);
console.error('Ошибка:', err);
}
}
/* async function readmessages(contactId) {
if (!contactId) return;
const token = localStorage.getItem('token');
try {
const res = await fetch(`/api/messages-read/${contactId}`, {
headers: { Authorization: `Bearer ${token}` }
});
if (res.ok) {
const data = await res.text();
if (data == "true") {
readmes('true'); // Есть непрочитанные
} else {
readmes('false'); // Нет непрочитанных
}
console.log('Статус прочитанности проверен:', data);
} else {
console.error('Ошибка проверки сообщений');
readmes('false');
}
} catch (err) {
console.error('Ошибка:', err);
readmes('false');
}
}*/
// Функция отправки сообщения
async function sendMessage() {
@@ -1431,10 +1484,11 @@ function Game({ avatarUrl, gender }) {
if (activeChat) {
// Первоначальная загрузка сообщений
loadMessages(activeChat.id);
//readmessages(activeChat.id)
// Запускаем интервал для проверки новых сообщений
const interval = setInterval(() => {
loadMessages(activeChat.id);
//readmessages(activeChat.id);
}, 1000); // Проверка каждую секунду
setMessageInterval(interval);
@@ -5589,7 +5643,7 @@ function Game({ avatarUrl, gender }) {
))}
</div>
{/* Область чата */}
<div style={{ flex: 1, display: isPhoneNarrow && !activeChat ? 'none' : 'flex', flexDirection: 'column', background: '#fff' }}>
<div style={{ flex: 1, display: isPhoneNarrow && !activeChat ? 'none' : 'flex', flexDirection: 'column', background: '#fff', overflowX: 'hidden', overflowY: 'auto' }}>
{activeChat && (
<>
<div style={{ padding: '8px 12px', borderBottom: '1px solid #eee', display: 'flex', alignItems: 'center', gap: 8 }}>
@@ -5599,24 +5653,55 @@ function Game({ avatarUrl, gender }) {
<span style={{ fontWeight: 600 }}>{activeChat.firstName} {activeChat.lastName}</span>
</div>
<div id="chatContainer" style={{ flex: 1, overflowY: 'auto', padding: 10, background: '#fafafa' }}>
{console.log("UserProfile ID:", activeChat.id, "Type:", typeof activeChat.id)}
{console.log("First message sender_id:", messages[0]?.sender_id, "Type:", typeof messages[0]?.sender_id)}
{messages.length === 0 ? (
<p style={{ textAlign: 'center', color: '#666' }}>Нет сообщений</p>
) : (
messages.map(msg => (
<div key={msg.id} style={{ display: 'flex', justifyContent: (msg.sender_id === userProfile?.id) ? 'flex-end' : 'flex-start', margin: '8px 0' }}>
<div style={{ maxWidth: '75%', background: (msg.sender_id === userProfile?.id) ? '#0084ff' : '#e5e5ea', color: (msg.sender_id === userProfile?.id) ? '#fff' : '#000', padding: '8px 12px', borderRadius: 12 }}>{msg.message}</div>
<div key={msg.id} style={{ display: 'flex', justifyContent: (Number(msg.sender_id) == Number(activeChat.id)) ? 'flex-start' : 'flex-end', margin: '8px 0' }}>
<div style={{
maxWidth: 'min(40%, 30ch)', // Ограничение и по % и по символам
background: (Number(msg.sender_id) == Number(activeChat.id)) ? '#e5e5ea' : '#0084ff',
color: (Number(msg.sender_id) == Number(activeChat.id)) ? '#000' : '#fff',
padding: '8px 12px',
borderRadius: 12,
wordWrap: 'break-word',
overflowWrap: 'break-word',
whiteSpace: 'pre-wrap'
}}>
{msg.message}
</div>
</div>
))
)}
</div>
<div style={{ padding: 8, display: 'flex', gap: 8, borderTop: '1px solid #eee', background: '#fff' }}>
<div style={{
padding: 8,
display: 'flex',
gap: 8,
borderTop: '1px solid #eee',
background: '#fff',
width: '100%'
}}>
<input
type="text"
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
placeholder="Сообщение"
onKeyDown={(e) => { if (e.key === 'Enter') sendMessage(); }}
style={{ flex: 1, width: '80%', padding: '8px 8px', borderRadius: 12, border: '1px solid #ddd' }} />
style={{
flex: 1,
padding: '8px 8px',
width: '80%',
maxWidth: '140px',
borderRadius: 12,
border: '1px solid #ddd',
boxSizing: 'border-box',
wordWrap: 'break-word',
whiteSpace: 'pre-wrap'
}}
/>
<button onClick={sendMessage} style={{ padding: '8px 8px', background: '#0084ff', color: '#fff', border: 'none', borderRadius: 12, cursor: 'pointer' }}></button>
</div>
</>