Добавил дизайн в чате
This commit is contained in:
92
server.js
92
server.js
@@ -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 || {};
|
||||
|
||||
145
src/Game.js
145
src/Game.js
@@ -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);
|
||||
@@ -5588,39 +5642,70 @@ function Game({ avatarUrl, gender }) {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* Область чата */}
|
||||
<div style={{ flex: 1, display: isPhoneNarrow && !activeChat ? 'none' : 'flex', flexDirection: 'column', background: '#fff' }}>
|
||||
{activeChat && (
|
||||
<>
|
||||
<div style={{ padding: '8px 12px', borderBottom: '1px solid #eee', display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
{isPhoneNarrow && (
|
||||
<button onClick={() => setActiveChat(null)} style={{ border: 'none', background: 'transparent', fontSize: 16, cursor: 'pointer' }}>←</button>
|
||||
)}
|
||||
<span style={{ fontWeight: 600 }}>{activeChat.firstName} {activeChat.lastName}</span>
|
||||
</div>
|
||||
<div id="chatContainer" style={{ flex: 1, overflowY: 'auto', padding: 10, background: '#fafafa' }}>
|
||||
{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>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<div style={{ padding: 8, display: 'flex', gap: 8, borderTop: '1px solid #eee', 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 }}>
|
||||
{isPhoneNarrow && (
|
||||
<button onClick={() => setActiveChat(null)} style={{ border: 'none', background: 'transparent', fontSize: 16, cursor: 'pointer' }}>←</button>
|
||||
)}
|
||||
<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: (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',
|
||||
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>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!activeChat && (
|
||||
<div style={{ margin: 'auto', color: '#666' }}>Выберите контакт</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user