File size: 9.28Kb
<?php
/**
* CMS: LaiCMS (v1.0 Edition 2026)
* File: messages/chat.php
* Оптимизация: PHP 7.4+, Mobile First, Haptic Feedback.
*/
declare(strict_types=1);
require_once '../system/db.php';
require_once '../system/functions.php';
// 1. Проверка авторизации через null-coalescing
$my_id = (int)($_SESSION['user_id'] ?? 0);
if (!$my_id) {
header("Location: /users/login.php");
exit;
}
// 2. Валидация ID собеседника
$opp_id = (int)($_GET['id'] ?? 0);
if ($opp_id <= 0) {
die("Пользователь не найден");
}
// 3. Получение данных собеседника через подготовленный запрос (Безопасность)
$stmt = $mysqli->prepare("SELECT username, avatar, last_seen, role FROM users WHERE id = ? LIMIT 1");
$stmt->bind_param("i", $opp_id);
$stmt->execute();
$opp = $stmt->get_result()->fetch_assoc();
if (!$opp) {
die("Собеседник не существует");
}
// 4. Статус онлайн и заголовок
$is_online = (strtotime($opp['last_seen'] ?? '') > (time() - 300));
$page_title = "Чат с " . htmlspecialchars($opp['username']);
include '../system/header.php';
?>
<style>
:root {
--chat-bg: #fdfdfd;
--my-bubble: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
--opp-bubble: var(--pico-card-background-color);
}
[data-theme="dark"] :root { --chat-bg: #0b0e14; }
/* Основной контейнер чата */
.chat-wrapper {
max-width: 1000px;
margin: 0 auto;
height: 85vh;
display: flex;
flex-direction: column;
background: var(--chat-bg);
border-radius: 24px;
border: 1px solid var(--pico-muted-border-color);
box-shadow: 0 20px 60px rgba(0,0,0,0.1);
overflow: hidden;
animation: chatFadeIn 0.5s ease;
}
/* Адаптация под мобильные устройства */
@media (max-width: 768px) {
.chat-wrapper {
height: 92vh; /* Больше места на экране телефона */
border-radius: 0;
border: none;
}
#msgInput {
font-size: 16px !important; /* Убирает авто-зум в iOS */
}
.send-btn {
width: 48px !important;
height: 48px !important;
}
}
/* Шапка */
.chat-header {
padding: 1rem 1.5rem;
background: var(--pico-card-sectioning-background-color);
border-bottom: 1px solid var(--pico-muted-border-color);
display: flex;
justify-content: space-between;
align-items: center;
backdrop-filter: blur(15px);
z-index: 10;
}
.user-info { display: flex; align-items: center; gap: 12px; }
.chat-avatar { width: 45px; height: 45px; border-radius: 14px; object-fit: cover; }
.status-badge { font-size: 0.75rem; display: flex; align-items: center; gap: 6px; }
.status-dot { width: 8px; height: 8px; border-radius: 50%; }
.online { background: #2ecc71; box-shadow: 0 0 10px rgba(46, 204, 113, 0.5); }
.offline { background: #95a5a6; }
/* Область сообщений */
.chat-body {
flex: 1;
overflow-y: auto;
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 12px;
background-image: radial-gradient(var(--pico-muted-border-color) 0.8px, transparent 0.8px);
background-size: 24px 24px;
scroll-behavior: smooth;
}
/* Пузыри сообщений */
.bubble {
max-width: 80%;
padding: 12px 16px;
border-radius: 20px;
font-size: 0.95rem;
position: relative;
line-height: 1.4;
}
.bubble.me {
align-self: flex-end;
background: var(--my-bubble);
color: #fff;
border-bottom-right-radius: 4px;
}
.bubble.opp {
align-self: flex-start;
background: var(--opp-bubble);
border: 1px solid var(--pico-muted-border-color);
border-bottom-left-radius: 4px;
}
/* Нижняя панель ввода */
.chat-footer {
padding: 1rem 1.5rem;
background: var(--pico-card-sectioning-background-color);
border-top: 1px solid var(--pico-muted-border-color);
}
#chatForm {
display: flex;
gap: 10px;
align-items: center;
background: var(--pico-background-color);
padding: 5px 10px;
border-radius: 30px;
border: 1px solid var(--pico-muted-border-color);
}
#msgInput { border: none !important; margin: 0 !important; flex: 1; background: transparent !important; }
.action-btn { background: none; border: none; color: var(--pico-primary); cursor: pointer; padding: 8px; }
.send-btn {
background: var(--pico-primary) !important;
color: white !important;
width: 40px; height: 40px; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
transition: 0.2s;
}
@keyframes chatFadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
</style>
<div class="chat-wrapper">
<div class="chat-header">
<div class="user-info">
<a href="/users/account.php?id=<?= $opp_id ?>">
<?php if(!empty($opp['avatar'])): ?>
<img src="<?= htmlspecialchars($opp['avatar']) ?>" class="chat-avatar">
<?php else: ?>
<div class="chat-avatar" style="background: var(--pico-primary); color:white; display:flex; align-items:center; justify-content:center;">
<?= mb_substr($opp['username'], 0, 1) ?>
</div>
<?php endif; ?>
</a>
<div>
<strong style="display:block;"><?= htmlspecialchars($opp['username']) ?></strong>
<span class="status-badge">
<div class="status-dot <?= $is_online ? 'online' : 'offline' ?>"></div>
<small><?= $is_online ? 'в сети' : 'был ' . date('H:i', strtotime($opp['last_seen'])) ?></small>
</span>
</div>
</div>
<button class="action-btn" onclick="location.reload()"><i class="fa-solid fa-rotate"></i></button>
</div>
<div class="chat-body" id="chatBox"></div>
<div class="chat-footer">
<form id="chatForm">
<input type="hidden" name="opp_id" value="<?= $opp_id ?>">
<label class="action-btn" style="margin:0;">
<i class="fa-solid fa-paperclip"></i>
<input type="file" name="attachment" id="file-input" hidden>
</label>
<input type="text" name="message" id="msgInput" placeholder="Сообщение..." autocomplete="off">
<button type="submit" class="send-btn">
<i class="fa-solid fa-paper-plane"></i>
</button>
</form>
<div id="file-preview" style="font-size:0.7rem; color:var(--pico-primary); margin-top:5px; margin-left:15px;"></div>
</div>
</div>
<script>
const chatBox = document.getElementById('chatBox');
const chatForm = document.getElementById('chatForm');
const msgInput = document.getElementById('msgInput');
const fileInput = document.getElementById('file-input');
// 1. Плавная прокрутка вниз
const scrollToBottom = (force = false) => {
const isAtBottom = chatBox.scrollHeight - chatBox.scrollTop <= chatBox.clientHeight + 150;
if (force || isAtBottom) {
chatBox.scrollTo({ top: chatBox.scrollHeight, behavior: 'smooth' });
}
};
// 2. Загрузка сообщений через AJAX
async function fetchMessages() {
try {
const res = await fetch(`get_messages_ajax.php?opp_id=<?= $opp_id ?>`);
const html = await res.text();
if (chatBox.innerHTML !== html) {
chatBox.innerHTML = html;
scrollToBottom();
}
} catch (e) { console.error("Update error"); }
}
// 3. Удобная отправка (телефон + ПК)
chatForm.onsubmit = async (e) => {
e.preventDefault();
if (!msgInput.value.trim() && !fileInput.files.length) return;
const formData = new FormData(chatForm);
msgInput.value = ''; // Мгновенная очистка
document.getElementById('file-preview').innerText = '';
// Тактильный отклик (для мобилок)
if (window.navigator.vibrate) window.navigator.vibrate(10);
await fetch('send_ajax.php', { method: 'POST', body: formData });
fetchMessages();
setTimeout(() => scrollToBottom(true), 50);
};
// 4. Отправка по нажатию Enter на клавиатуре телефона/ПК
msgInput.onkeydown = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
chatForm.requestSubmit();
}
};
// 5. Превью выбранного файла
fileInput.onchange = () => {
if (fileInput.files[0]) {
document.getElementById('file-preview').innerText = "📎 " + fileInput.files[0].name;
}
};
// Интервалы и инициализация
setInterval(fetchMessages, 3000);
fetchMessages();
setTimeout(() => scrollToBottom(true), 500);
</script>
<?php include '../system/footer.php'; ?>