xgit simple git

nexus

nexus

clone git clone https://kb.hax.al/nexus

messages/index.php

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'; ?>