1 <?php 2 require_once __DIR__ . '/../includes/bootstrap.php'; 3 must_login(); 4 5 $box = get('box', 'inbox'); 6 if (!in_array($box, ['inbox', 'sent'])) $box = 'inbox'; 7 8 /* ── Fetch messages ─────────────────────────────────── */ 9 if ($box === 'inbox') { 10 $msgs = DB::rows(" 11 SELECT m.*, u.username AS other_name, u.avatar AS other_av, 12 u.last_seen, u.role 13 FROM messages m 14 JOIN users u ON u.id = m.sender_id 15 WHERE m.receiver_id = ? AND m.deleted_by_receiver = 0 16 ORDER BY m.created_at DESC 17 ", [$USER['id']]); 18 } else { 19 $msgs = DB::rows(" 20 SELECT m.*, u.username AS other_name, u.avatar AS other_av, 21 u.last_seen, u.role 22 FROM messages m 23 JOIN users u ON u.id = m.receiver_id 24 WHERE m.sender_id = ? AND m.deleted_by_sender = 0 25 ORDER BY m.created_at DESC 26 ", [$USER['id']]); 27 } 28 29 /* ── Online users (last seen within 15 min) ─────────── */ 30 $since = DB::sinceSeconds(900); 31 $onlineUsers = DB::rows( 32 "SELECT id, username, avatar, role FROM users 33 WHERE last_seen >= $since AND id != ? AND suspended = 0 34 ORDER BY last_seen DESC LIMIT 20", 35 [$USER['id']] 36 ); 37 38 $unread = unread_messages(); 39 $sentCount = (int)DB::val("SELECT COUNT(*) FROM messages WHERE sender_id=? AND deleted_by_sender=0", [$USER['id']]); 40 41 $PAGE_TITLE = 'Messages'; 42 include __DIR__ . '/../views/partials/layout.php'; 43 ?> 44 45 <div class="mx-layout"> 46 47 <!-- ── Left: Inbox ───────────────────────────── --> 48 <div class="mx-main"> 49 50 <div class="mx-topbar"> 51 <div> 52 <h1 class="mx-title"> 53 <?= $box === 'inbox' ? '📥 Inbox' : '📤 Sent' ?> 54 <?php if ($box === 'inbox' && $unread > 0): ?> 55 <span class="mx-unread-badge"><?= $unread ?> unread</span> 56 <?php endif; ?> 57 </h1> 58 </div> 59 <a href="<?= u('messages/compose.php') ?>" class="btn-primary"> 60 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M12 5v14M5 12h14"/></svg> 61 Compose 62 </a> 63 </div> 64 65 <!-- Tabs --> 66 <div class="mx-tabs"> 67 <a href="?box=inbox" class="mx-tab <?= $box === 'inbox' ? 'active' : '' ?>"> 68 📥 Inbox 69 <?php if ($unread > 0): ?> 70 <span class="mx-tab-badge"><?= $unread ?></span> 71 <?php endif; ?> 72 </a> 73 <a href="?box=sent" class="mx-tab <?= $box === 'sent' ? 'active' : '' ?>"> 74 📤 Sent 75 <?php if ($sentCount > 0): ?> 76 <span class="mx-tab-badge muted"><?= $sentCount ?></span> 77 <?php endif; ?> 78 </a> 79 </div> 80 81 <?php if (isset($_GET['sent'])): ?> 82 <div class="alert ok" style="margin-bottom:16px">✓ Message sent successfully.</div> 83 <?php endif; ?> 84 85 <?php if (empty($msgs)): ?> 86 <div class="mx-empty"> 87 <div class="mx-empty-icon"><?= $box === 'inbox' ? '📭' : '📤' ?></div> 88 <p><?= $box === 'inbox' ? 'Your inbox is empty.' : 'No sent messages yet.' ?></p> 89 <a href="<?= u('messages/compose.php') ?>" class="btn-primary">Send a message</a> 90 </div> 91 <?php else: ?> 92 <div class="mx-list"> 93 <?php foreach ($msgs as $m): 94 $isUnread = !$m['is_read'] && $box === 'inbox'; 95 $isOnline = strtotime($m['last_seen']) >= (time() - 900); 96 $name = $m['other_name']; 97 $av = $m['other_av']; 98 ?> 99 <a href="<?= u('messages/view.php?id=' . $m['id']) ?>" 100 class="mx-row <?= $isUnread ? 'unread' : '' ?>"> 101 102 <!-- Avatar + online dot --> 103 <div class="mx-row-av"> 104 <?php if ($av): ?> 105 <img src="<?= e($av) ?>" class="av-md" alt=""> 106 <?php else: ?> 107 <span class="av-md av-init"><?= strtoupper($name[0]) ?></span> 108 <?php endif; ?> 109 <?php if ($isOnline): ?> 110 <span class="mx-online-dot" title="Online now"></span> 111 <?php endif; ?> 112 </div> 113 114 <!-- Content --> 115 <div class="mx-row-body"> 116 <div class="mx-row-top"> 117 <span class="mx-row-from"> 118 <?= $box === 'inbox' ? 'From' : 'To' ?> 119 <strong>@<?= e($name) ?></strong> 120 <span class="role-tag role-<?= e($m['role']) ?>"><?= e($m['role']) ?></span> 121 <?php if ($isOnline): ?> 122 <span class="mx-online-label">● Online</span> 123 <?php endif; ?> 124 </span> 125 <span class="mx-row-time"> 126 <?php if ($isUnread): ?> 127 <span class="mx-new-badge">NEW</span> 128 <?php endif; ?> 129 <span class="ago" data-ts="<?= e($m['created_at']) ?>"></span> 130 </span> 131 </div> 132 <?php if ($m['subject']): ?> 133 <div class="mx-row-subject"><?= e($m['subject']) ?></div> 134 <?php endif; ?> 135 <div class="mx-row-preview"><?= e(mb_substr($m['body'], 0, 100)) ?><?= mb_strlen($m['body']) > 100 ? '…' : '' ?></div> 136 </div> 137 138 <!-- Arrow --> 139 <div class="mx-row-arrow">›</div> 140 </a> 141 <?php endforeach; ?> 142 </div> 143 <?php endif; ?> 144 </div> 145 146 <!-- ── Right: Online users + quick compose ───── --> 147 <aside class="mx-aside"> 148 149 <!-- Online users --> 150 <div class="mx-widget"> 151 <div class="mx-widget-head"> 152 <span class="mx-online-dot" style="position:static;margin-right:4px"></span> 153 Online Now 154 <span class="mx-widget-count"><?= count($onlineUsers) ?></span> 155 </div> 156 <?php if (empty($onlineUsers)): ?> 157 <div class="mx-widget-empty">No one online right now</div> 158 <?php else: ?> 159 <div class="mx-online-list"> 160 <?php foreach ($onlineUsers as $ou): ?> 161 <div class="mx-online-row"> 162 <div style="position:relative;flex-shrink:0"> 163 <?php if ($ou['avatar']): ?> 164 <img src="<?= e($ou['avatar']) ?>" class="av-sm" alt=""> 165 <?php else: ?> 166 <span class="av-sm av-init"><?= strtoupper($ou['username'][0]) ?></span> 167 <?php endif; ?> 168 <span class="mx-online-dot mx-online-dot-sm"></span> 169 </div> 170 <div class="mx-online-info"> 171 <a href="<?= u('users/profile.php?u=' . urlencode($ou['username'])) ?>" 172 class="mx-online-name">@<?= e($ou['username']) ?></a> 173 <span class="role-tag role-<?= e($ou['role']) ?>"><?= e($ou['role']) ?></span> 174 </div> 175 <a href="<?= u('messages/compose.php?to=' . urlencode($ou['username'])) ?>" 176 class="mx-msg-quick" title="Send message"> 177 <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg> 178 </a> 179 </div> 180 <?php endforeach; ?> 181 </div> 182 <?php endif; ?> 183 </div> 184 185 <!-- Quick compose --> 186 <div class="mx-widget" style="margin-top:16px"> 187 <div class="mx-widget-head">✏️ Quick Message</div> 188 <div style="padding:14px"> 189 <div style="position:relative;margin-bottom:10px"> 190 <input type="text" id="quickTo" class="fi" placeholder="@username" autocomplete="off" 191 style="padding-left:26px"> 192 <span style="position:absolute;left:10px;top:50%;transform:translateY(-50%);color:var(--faint);font-size:13px;pointer-events:none">@</span> 193 <div id="quickSuggestions" class="to-suggestions"></div> 194 </div> 195 <a id="quickComposeBtn" href="<?= u('messages/compose.php') ?>" class="btn-primary btn-block"> 196 Compose → 197 </a> 198 </div> 199 </div> 200 201 </aside> 202 </div> 203 204 <script> 205 /* Quick compose — update link as user types */ 206 var qTo = document.getElementById('quickTo'); 207 var qBtn = document.getElementById('quickComposeBtn'); 208 var qSug = document.getElementById('quickSuggestions'); 209 var qTimer; 210 211 if (qTo) { 212 qTo.addEventListener('input', function() { 213 var val = this.value.trim().replace(/^@/, ''); 214 qBtn.href = NX.base + '/messages/compose.php' + (val ? '?to=' + encodeURIComponent(val) : ''); 215 clearTimeout(qTimer); 216 if (val.length < 1) { qSug.style.display = 'none'; return; } 217 qTimer = setTimeout(function() { 218 fetch(NX.base + '/api/search_users.php?q=' + encodeURIComponent(val)) 219 .then(function(r) { return r.json(); }) 220 .then(function(rows) { 221 if (!rows.length) { qSug.style.display = 'none'; return; } 222 qSug.innerHTML = rows.slice(0, 5).map(function(u) { 223 return '<div class="to-sug-item" onclick="pickQuick(\'' + u.username + '\')">@' + u.username + '</div>'; 224 }).join(''); 225 qSug.style.display = 'block'; 226 }); 227 }, 220); 228 }); 229 document.addEventListener('click', function(e) { 230 if (!qTo.contains(e.target)) qSug.style.display = 'none'; 231 }); 232 } 233 234 function pickQuick(uname) { 235 qTo.value = uname; 236 qBtn.href = NX.base + '/messages/compose.php?to=' + encodeURIComponent(uname); 237 qSug.style.display = 'none'; 238 qBtn.click(); 239 } 240 </script> 241 242 <?php include __DIR__ . '/../views/partials/layout_end.php'; ?>